/**
 * File Name: Aquarium.java
 *
 * Date: 18/12/2009
 *
 * Copyright (c) 2006 Michael LaLena. All rights reserved.  (www.lalena.com)
 * Permission to use, copy, modify, and distribute this Program and its documentation,
 *  if any, for any purpose and without fee is hereby granted, provided that:
 *   (i) you not charge any fee for the Program, and the Program not be incorporated
 *       by you in any software or code for which compensation is expected or received;
 *   (ii) the copyright notice listed above appears in all copies;
 *   (iii) both the copyright notice and this Agreement appear in all supporting documentation; and
 *   (iv) the name of Michael LaLena or lalena.com not be used in advertising or publicity
 *          pertaining to distribution of the Program without specific, written prior permission.
 */
package model;

import gui.Constanten;
import java.util.Vector;
import java.awt.*;
import java.util.Random;

/**
 *
 * Deze klasse is de verzameling van alle objecten die men terugvindt in het aquarium.
 * Daarnaast bevat ze een aantal controlestructuren voor de detectie van nabijgelegen
 * aquariumobjecten en de reactie hierop.
 *
 * @author Michiel Declercq
 * @author Stijn Delbeke
 * @author Dieter Deriemaeker
 * @author Gregory Nickmans
 *
 * @author Michael LaLena
 *
 */
public class Aquarium {

    /**
     * Totaal aantal Vissen in het aquarium
     */
    public static int aantalVissen = 0;

    /**
     * Totaal aantal Orka's in het aquarium
     */
    public static int aantalOrkas = 0;

    /**
     * Random generator.
     * Static gemaakt om de getallen zo random mogelijk te genereren.
     */
    private static Random rand = new Random(System.nanoTime());

    /**
     * Aantal dieren binnen het bereik van een ander dier
     */
    private int numDieren;

    /**
     * Verhoog numDieren met 1
     */
    public void verhoogNumDieren() {
        numDieren++;
    }
    /**
     * Deze vector bevat alle elementen van het type AquariumObject die in het
     * aquarium aanwezig zijn
     */
    private Vector<AquariumObject> aquariumObjecten;

    /**
     * Constructor
     */
    public Aquarium() {
        aquariumObjecten = new Vector<AquariumObject>(40, 10);
    }

    /**
     * Voeg een AquariumObject toe aan het aquarium
     *
     * @param aquariumObject Toe te voegen object
     */
    public void addObject(AquariumObject aquariumObject) {
        aquariumObjecten.addElement(aquariumObject);
    }

    /**
     * Geeft het AquariumObject op de i-de plaats in de vector terug
     *
     * @param i Plaats van het terug te geven object
     * @return  AquariumObject
     */
    public AquariumObject getObject(int i) {
        return aquariumObjecten.get(i);
    }

    /**
     * Geeft het totaal aantal objecten aanwezig in het aquarium
     *
     * @return  int
     */
    public int getTotaalObjecten() {
        return aquariumObjecten.size();
    }

    /**
     * Geeft de vector met aquariumobjecten terug
     *
     * @return Vector<AquariumObject>
     */
    public Vector<AquariumObject> getAquariumObjecten() {
        return aquariumObjecten;
    }

    /**
     * Laat elk dier bewegen in de richting van generalHeading
     *
     */
    synchronized public void moveObjects() {
        int huidigObject = 0;
        int teKruisenObject = 0;

        // Overloop alle dieren
        while (huidigObject < aquariumObjecten.size()) {

            AquariumObject aquariumObject = aquariumObjecten.elementAt(huidigObject);
            if (aquariumObject instanceof Dier) {
                Dier dier;
                if (aquariumObject instanceof Vis) {
                    dier = (Vis) aquariumObject;
                }
                else {
                    dier = (Orka) aquariumObject;
                }

                int gh = generalHeading(dier);

                aquariumObject.move(gh);

                teKruisenObject = 0;

                // Overloop alle andere dieren om te controleren als deze zich op dezelfde locatie
                // als het huidige dier bevinden.
                while (teKruisenObject < aquariumObjecten.size()) {
                    AquariumObject anderObject = aquariumObjecten.elementAt(teKruisenObject);

                    // Bevinden beide dieren zich op dezelfde locatie (en zijn ze niet één en dezelfde)?
                    if ((huidigObject != teKruisenObject)
                            && (dier.getLocation().x > anderObject.getLocation().x - 2)
                            && (dier.getLocation().x < anderObject.getLocation().x + 2)
                            && (dier.getLocation().y > anderObject.getLocation().y - 2)
                            && (dier.getLocation().y < anderObject.getLocation().y + 2)) {

                        // Dit dier is een Orka en het andere een vis
                        // -> Vis wordt opgegeten
                        if ((dier instanceof Orka) && (anderObject instanceof Vis)) {
                            aquariumObjecten.removeElement(anderObject);
                            aantalVissen--;
                            System.out.println("Nog " + aantalVissen + " vissen");
                            break;
                        }
                    }
                    teKruisenObject++;

                }

            }
            huidigObject++;

        }


    }

    /**
     *
     * Deze functie berekent de richting waarnaar een Dier zich zal keren na deze stap.
     * Het dier kijkt naar alle andere dieren en obstakels in het aquarium en controleert
     * als deze zich binnen zijn detection range bevinden.
     * Orka's bewegen zich naar de Vissen toe.
     * Vissen zwemmen van Orka's weg, maar groeperen zich wel onderling.
     * Zowel Orka's als Vissen proberen Obstakels te vermijden.
     *
     * @param  dier Het dier om de richting voor te berekenen
     *
     * @return int
     */
    private int generalHeading(Dier dier) {

        // Tel de punten van alle dieren en obstakels binnen de detection range op
        Point target = new Point(Constanten.CANVAS_WIDTH/2, Constanten.CANVAS_HEIGHT/2);

        numDieren = 0;

        boolean bevatObstakel = false;

        //Controleer voor elk object in het aquarium als het zich binnen
        //onze detection range bevindt
        for (AquariumObject anderObject : aquariumObjecten) {

            if (anderObject instanceof Obstakel) {
                bevatObstakel = true;
            }

            if (dier instanceof Vis) {
                Vis vis = (Vis) dier;
                target = vis.Handling(this, anderObject, target);

            }
            else {
                Orka orka = (Orka) dier;
                target = orka.Handling(anderObject, target);
            }

        }
        // Als er geen andere vissen of obstakels in de buurt zijn van deze vis,
        // moet de richting niet veranderd worden
        if (dier instanceof Vis && !bevatObstakel) {
            if (numDieren == 0) {
                return dier.getCurrentTheta();
            }
            // Er zijn andere vissen in onze buurt gedetecteerd -> aligneren
            else {
                target = dier.sumPoints(dier.location, 1.0, target, 1 / (double) numDieren);

            }
        }

        // Doellocatie omzetten naar een hoek in graden
        int targetTheta = (int) (180 / Math.PI * Math.atan2(dier.location.y - target.y, target.x - dier.location.x));

        // Ons ervan verzekeren dat de hoek tussen 0 en 360 graden ligt
        // Dit is de hoek waarnaar het dier zal bijsturen
        return (targetTheta + 360) % 360;

    }

    /**
     * Voeg objecten toe aan het aquarium.
     * De objecten worden op een random positie op de canvas geplaatst.
     *
     * @param aantal_dieren Aantal toe te voegen dieren
     * @param aantal_obstakels  Aantal toe te voegen obstakels
     */
    public void voegAquariumObjectenToe(int aantal_dieren, int aantal_obstakels) {
        for (int i = 0; i < aantal_dieren; i++) {

            int x = (int) (rand.nextDouble() * Constanten.CANVAS_WIDTH);
            int y = (int) (rand.nextDouble() * Constanten.CANVAS_HEIGHT);
            //Het aantal orka's is willekeurig, doch beperkt
            if (rand.nextInt(100) > Constanten.MAX_AANTAL_ORKAS) {
                this.addObject(new Vis(x, y));
            }
            else {
                this.addObject(new Orka(x, y));
            }
        }
        //Er moet minimum één orka in het spel zijn!
        if (aantalOrkas == 0) {
            int x = (int) (rand.nextDouble() * Constanten.CANVAS_WIDTH);
            int y = (int) (rand.nextDouble() * Constanten.CANVAS_HEIGHT);
            this.addObject(new Orka(x, y));
        }

        for (int i = 0; i < aantal_obstakels; i++) {
            int x = (int) (rand.nextInt(Constanten.CANVAS_WIDTH - Constanten.MAX_GROOTTE_OBSTAKEL.width));
            int y = (int) (rand.nextInt(Constanten.CANVAS_HEIGHT - Constanten.MAX_GROOTTE_OBSTAKEL.height));
            int w = (int) (rand.nextInt(Constanten.MAX_GROOTTE_OBSTAKEL.width) + Constanten.MIN_GROOTTE_OBSTAKEL.width);
            int h = (int) (rand.nextInt(Constanten.MAX_GROOTTE_OBSTAKEL.height) + Constanten.MIN_GROOTTE_OBSTAKEL.height);

            Dimension dim = new Dimension(w, h);
            voegObstakelToe(x, y, dim);
        }

        //Linkerrand
        for (int i = 0; i < Constanten.CANVAS_HEIGHT; i += 10) {
            int x = 0;
            int y = i;
            int h = Constanten.CANVAS_HEIGHT / 10;
            int w = 10;
            voegObstakelToe(x, y, new Dimension(w, h));
        }

        //Rechterrand
        for (int i = 0; i < Constanten.CANVAS_HEIGHT; i += 10) {
            int x = Constanten.CANVAS_WIDTH - 10;
            int y = i;
            int h = Constanten.CANVAS_HEIGHT / 10;
            int w = 10;
            voegObstakelToe(x, y, new Dimension(w, h));
        }

        //Bovenrand
        for (int i = 0; i < Constanten.CANVAS_HEIGHT; i += 10) {
            int x = i;
            int y = 0;
            int h = 10;
            int w = Constanten.CANVAS_WIDTH / 10;
            voegObstakelToe(x, y, new Dimension(w, h));
        }

        //Onderrand
        for (int i = 0; i < Constanten.CANVAS_HEIGHT; i += 10) {
            int x = i;
            int y = Constanten.CANVAS_HEIGHT - 10;
            int h = 10;
            int w = Constanten.CANVAS_WIDTH / 10;
            voegObstakelToe(x, y, new Dimension(w, h));
        }

    }

    /**
     * Voeg een obstakel toe aan het aquarium
     *
     * @param x x-coördinaat
     * @param y y-coördinaat
     * @param dim dimensies van het toe te voegen obstakel
     */
    public void voegObstakelToe(int x, int y, Dimension dim) {
        this.addObject(new Obstakel(x, y, dim, Constanten.KLEUR_OBSTAKEL));
    }

    /**
     * Voeg een willekeurig stromingsgebied toe aan het aquarium
     */
    public void voegStromingenToe() {
        for (int i = 0; i < Constanten.AANTAL_STROMINGEN; i++) {
            new Stroming();
        }
    }
}
